﻿using System;
using System.Collections.Generic;
using System.Linq;

using Microsoft.Xna.Framework;

namespace WorldOnFire
{
    public class CCollisionManager
    {
        #region Correction Data

        // Correction structure
        private class CCorrection
        {
            // Object data
            public ABCMovingObject MovingObject;
            public int nObjectIndex;

            // The number of collisions detected
            public int nCorrectionCount;

            // The largest tunneling correction
            public Vector3 vTunnelingCorrection;

            // The cumulative correction vector from all non-tunneling collisions
            public Vector3 vCorrection;

            // You cannot has default constructor
            private CCorrection()
            {

            }

            public CCorrection(ABCMovingObject _MovingObject, int _nObjectIndex)
            {
                MovingObject = _MovingObject;
                nObjectIndex = _nObjectIndex;

                nCorrectionCount = 0;
                vTunnelingCorrection = Vector3.Zero;
                vCorrection = Vector3.Zero;
            }

            public void AddTunnelingCorrection(Vector3 _vTunnelingCorection)
            {
                // If the new correction's length is greater than the current correction
                if (_vTunnelingCorection.LengthSquared() > vTunnelingCorrection.LengthSquared())
                {
                    // Replace the old tunneling correction with the new one
                    vTunnelingCorrection = _vTunnelingCorection;
                }
            }

            public void AddCollisionCorrection(Vector3 _vCorrection)
            {
                // Add the correction to the cumulative vector
                vCorrection += _vCorrection;

                // Increment the number of collisions corrected for
                ++nCorrectionCount;
            }
        }

        // Correction list
        private List<CCorrection> m_CorrectionList;

        #endregion Correction Data

        #region Manager Data

        // Object lists
        private List<ABCObject> m_ObjectList;
        private List<ABCMovingObject> m_MovingObjectList;

        #endregion

        #region Initialization

        public void Initialize(List<ABCObject> _ObjectList, 
            List<ABCMovingObject> _MovingObjectList)
        {
            m_CorrectionList = new List<CCorrection>();

            m_ObjectList = _ObjectList;
            m_MovingObjectList = _MovingObjectList;
        }

        public void Shutdown()
        {

        }

        #endregion Initialization

        #region Update

        public void CheckCollisions()
        {
#if false
            ////////////////////////////////////////////////////////////////////////////////////////
            /* BEGIN: Tunneling checks ===========================================================*/

            // Loop through each moving object class
            for (int iMovingObjectClass = 0; 
                iMovingObjectClass < m_MovingObjectList.Count; ++iMovingObjectClass)
            {
                // Store the moving object class and its active indices locally
                ABCMovingObject MovingObject = m_MovingObjectList[iMovingObjectClass];
                List<int> TunnelingIndices = MovingObject.GetTunnelingIndexList();

                // If none of the objects contained in this class tunneled
                if (TunnelingIndices.Count == 0)
                {
                    // Skip this class
                    continue;
                }

                // Store the list of collidable types locally
                List<ABCObject.eObjectType> CollidableTypeIndices = MovingObject.GetCollidableTypeList();

                ////////////////////////////////////////////////////////////////////////////////////
                /* BEGIN: Check against each collidable type =====================================*/

                // Loop through every collidable class
                for (int iCollidableType = 0; 
                    iCollidableType < CollidableTypeIndices.Count; ++iCollidableType)
                {
                    // Store the other collidable class and its active indices locally
                    ABCObject CollidableObject = 
                        m_ObjectList[(int)CollidableTypeIndices[iCollidableType]];
                    List<int> CollidableObjectActiveIndices = CollidableObject.GetActiveIndexList();

                    // If the object classes are different, initialize the inner loop to 0
                    if (MovingObject != CollidableObject)
                    {
                        // Loop through each active object contained by the moving object class
                        for (int iMovingObject = 0;
                            iMovingObject < TunnelingIndices.Count; ++iMovingObject)
                        {
                            // Loop through each active object contained by the collidable class
                            for (int iCollidableObject = 0;
                            iCollidableObject < CollidableObjectActiveIndices.Count; ++iCollidableObject)
                            {
                                // Check for tunneling
                                this.CheckForTunneling(MovingObject, TunnelingIndices[iMovingObject],
                                    CollidableObject, CollidableObjectActiveIndices[iCollidableObject]);
                            }
                        }
                    }
                    // If the object classes are the same, initialize the inner loop to iMovingObject + 1
                    else
                    {
                        // Loop through each active object contained by the moving object class
                        for (int iMovingObject = 0;
                            iMovingObject < TunnelingIndices.Count; ++iMovingObject)
                        {
                            // Loop through each active object contained by the collidable class
                            for (int iCollidableObject = iMovingObject + 1;
                            iCollidableObject < CollidableObjectActiveIndices.Count; ++iCollidableObject)
                            {
                                // Check for tunneling
                                this.CheckForTunneling(MovingObject, TunnelingIndices[iMovingObject],
                                    CollidableObject, CollidableObjectActiveIndices[iCollidableObject]);
                            }
                        }
                    }


                }

                /* END: Check against each collidable type =======================================*/
                ////////////////////////////////////////////////////////////////////////////////////
            }

            /* END: Tunneling checks =============================================================*/
            ////////////////////////////////////////////////////////////////////////////////////////
#endif

            ////////////////////////////////////////////////////////////////////////////////////////
            /* BEGIN: Collision checks ===========================================================*/

            // Loop through each moving object class
            for (int iMovingObjectClass = 0;
                iMovingObjectClass < m_MovingObjectList.Count; ++iMovingObjectClass)
            {
                // Store the moving object class and its active indices locally
                ABCMovingObject MovingObject = m_MovingObjectList[iMovingObjectClass];
                List<int> ActiveIndices = MovingObject.GetActiveIndexList();

                // If none of the objects contained in this class tunneled
                if (ActiveIndices.Count == 0)
                {
                    // Skip this class
                    continue;
                }

                // Store the list of collidable types locally
                List<ABCObject.eObjectType> CollidableTypeIndices = MovingObject.GetCollidableTypeList();

                ////////////////////////////////////////////////////////////////////////////////////
                /* BEGIN: Check against each collidable type =====================================*/

                // Loop through every collidable class
                for (int iCollidableType = 0;
                    iCollidableType < CollidableTypeIndices.Count; ++iCollidableType)
                {
                    // Store the other collidable class and its active indices locally
                    ABCObject CollidableObject =
                        m_ObjectList[(int)CollidableTypeIndices[iCollidableType]];
                    List<int> CollidableObjectActiveIndices = CollidableObject.GetActiveIndexList();

                    // If the object classes are different, initialize the inner loop to 0
                    if (MovingObject != CollidableObject)
                    {
                        // Loop through each active object contained by the moving object class
                        for (int iMovingObject = 0;
                            iMovingObject < ActiveIndices.Count; ++iMovingObject)
                        {
                            // Loop through each active object contained by the collidable class
                            for (int iCollidableObject = 0;
                            iCollidableObject < CollidableObjectActiveIndices.Count; ++iCollidableObject)
                            {
                                // Check for tunneling
                                this.CheckForCollision(MovingObject, ActiveIndices[iMovingObject],
                                    CollidableObject, CollidableObjectActiveIndices[iCollidableObject]);
                            }
                        }
                    }
                    // If the object classes are the same, initialize the inner loop to iMovingObject + 1
                    else
                    {
                        // Loop through each active object contained by the moving object class
                        for (int iMovingObject = 0;
                            iMovingObject < ActiveIndices.Count; ++iMovingObject)
                        {
                            // Loop through each active object contained by the collidable class
                            for (int iCollidableObject = iMovingObject + 1;
                            iCollidableObject < CollidableObjectActiveIndices.Count; ++iCollidableObject)
                            {
                                // Check for tunneling
                                this.CheckForCollision(MovingObject, ActiveIndices[iMovingObject],
                                    CollidableObject, CollidableObjectActiveIndices[iCollidableObject]);
                            }
                        }
                    }


                }

                /* END: Check against each collidable type =======================================*/
                ////////////////////////////////////////////////////////////////////////////////////
            }

            /* END: Collision checks =============================================================*/
            ////////////////////////////////////////////////////////////////////////////////////////
        }

        public void ApplyCorrections()
        {
            // Loop through each object that needs correction
            for (int iCorrection = 0; iCorrection < m_CorrectionList.Count; ++iCorrection)
            {
                // Apply its correction
                m_CorrectionList[iCorrection].MovingObject.ApplyCollisionCorrection(
                    m_CorrectionList[iCorrection].nObjectIndex, m_CorrectionList[iCorrection].vCorrection);
            }

            // Clear the correction list
            m_CorrectionList.Clear();
        }

        #endregion Update

        #region Helper Functions

        private void CheckForTunneling(ABCMovingObject _MovingObject, int _nMovingObjectIndex,
            ABCObject _Object, int _nObjectIndex)
        {

        }

        private void CheckForCollision(ABCMovingObject _MovingObject, int _nMovingObjectIndex,
            ABCObject _Object, int _nObjectIndex)
        {
            // Store the object positions and radii
            Vector3 vMovingObjectPosition = _MovingObject.GetPosition(_nMovingObjectIndex);
            Vector3 vObjectPosition = _Object.GetPosition(_nObjectIndex);
            float fCombinedRadius = _MovingObject.GetRadius(_nMovingObjectIndex) 
                + _Object.GetRadius(_nObjectIndex);

            // TODO: Check correction list for moving object tunneling

            // Calculate the vector between the objects
            Vector3 vBetween = vMovingObjectPosition - vObjectPosition;

            // TODO: Replace this shit with a real collision check
            // If the distance between the objects is less than their combined radii
            if (vBetween.LengthSquared() < fCombinedRadius * fCombinedRadius)
            {
                // Trigger collision responses
                _Object.RespondToCollision(_nObjectIndex, _MovingObject, _nMovingObjectIndex);

                // If the object is supposed to react positionally
                if (_MovingObject.RespondToCollision(_nMovingObjectIndex, _Object, _nObjectIndex) == true)
                {
                    // Get the distance between the two objects
                    float fDistanceBetween = vBetween.Length();

                    // Normalize the correction vector
                    Vector3 vCorrection = vBetween /= fDistanceBetween;

                    // Scale the correction vector by the appropriate amount 
                    vCorrection *= (fCombinedRadius - fDistanceBetween);

                    // Add the correction to the correction list
                    this.AddCorrection(_MovingObject, _nMovingObjectIndex, vCorrection);
                }
            }
        }

        private void AddCorrection(ABCMovingObject _Object, int _nObjectIndex, Vector3 _vCorrection)
        {
            // Loop through the correction list to see if the object is already present
            for (int iCorrection = 0; iCorrection < m_CorrectionList.Count; ++iCorrection)
            {
                // If the object was found in the correction list
                if(m_CorrectionList[iCorrection].MovingObject == _Object
                    && m_CorrectionList[iCorrection].nObjectIndex == _nObjectIndex)
                {
                    // Apply the correction vector
                    m_CorrectionList[iCorrection].AddCollisionCorrection(_vCorrection);

                    // Bail out
                    return;
                }
            }

            // If the list does not already contain the object, create a new correction
            CCorrection Correction = new CCorrection(_Object, _nObjectIndex);

            // Apply the correction vector
            Correction.AddCollisionCorrection(_vCorrection);

            // Add the correction to the list
            m_CorrectionList.Add(Correction);
        }

        #endregion Helper Functions
    }
}
